﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace PDX
{
    /// <summary>
    /// A default <see cref="IPropertyResolver"/>
    /// </summary>
    public class DefaultPropertyResolver : IPropertyResolver
    {
        /// <summary>
        /// Gets or sets the default instance.
        /// </summary>
        /// <value>
        /// The default instance.
        /// </value>
        public static DefaultPropertyResolver DefaultInstance { get; set; }

        static DefaultPropertyResolver()
        {
            DefaultPropertyResolver.DefaultInstance = new DefaultPropertyResolver();
        }
        

        /// <summary>
        /// Resolves the property path on the specified viewmodel.
        /// </summary>
        /// <param name="viewmodel">The viewmodel.</param>
        /// <param name="propertyPath">The property path.</param>
        /// <returns></returns>
        public virtual IPropertyResolution Resolve(object viewmodel, string propertyPath)
        {
            var retval = new DefaultPropertyResolution();
            retval.OriginalDC = viewmodel;
            retval.PropertyPath = propertyPath;

            // split a complex path into its parts (e.g. Child.Subchild.Property)
            var parts = propertyPath.Split('.');

            object target = viewmodel;
            PropertyInfo prop = null;
            object newTarget = null;

            foreach(var part in parts)
            {
                if (newTarget != null)
                {
                    target = newTarget;
                }

                if (!ResolvePart(target, part, out prop, out newTarget, out target))
                {
                    throw new Exception("no public property " + propertyPath +
                        " was found for viewmodel " + viewmodel.ToString());
                }
            }

            retval.ResolvedDC = target;
            retval.Property = prop;

            return retval;
        }

        /// <summary>
        /// Resolves a single part of a property path.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="part">The part.</param>
        /// <param name="resolvedProp">The resolved prop.</param>
        /// <param name="propValue">The prop value.</param>
        /// <param name="resolvedTarget">The resolved target.</param>
        /// <returns></returns>
        protected virtual bool ResolvePart(object target, string part,
            out PropertyInfo resolvedProp, out object propValue, out object resolvedTarget)
        {
            var prop = target.GetType().GetProperties().FirstOrDefault(p => p.Name == part);
            if (prop != null)
            {
                propValue = prop.GetValue(target, null);
                resolvedProp = prop;
                resolvedTarget = target;
                return true;
            }
            else
            {
                var propSources = target.GetType().GetProperties();
                foreach (var propSource in propSources)
                {
                    if (!propSource.HasSingleAttribute<PropertySourceAttribute>())
                    {
                        continue;
                    }

                    var potentialTarget = propSource.GetValue(target, null);

                    if (ResolvePart(potentialTarget, part, out resolvedProp, out propValue, out resolvedTarget))
                    {
                        return true;
                    }
                }
            }

            propValue = null;
            resolvedProp = null;
            resolvedTarget = null;
            return false;
        }

    }
}
